/* Janus Chat - Version 1.0
 * Authors: Matthew Perry
 *          Michael Freid
 *          Marc Latou
 *          Richard Lee
 *
 * The Janus Chat Client is a highly secured
 * peer-to-peer chat client designed to facilitate
 * the transfer of sensitive data or as a communication
 * medium for the extra paranoid
 *
 * This is the main console for the server */

// DEFINES
//#define KEEP_ALIVE_MESSAGE_TYPE 8
//#define REQUEST_MESSAGE_TYPE 6
//#define INFO_MESSAGE_TYPE 7
//#define SERVER_NAME "SERVER"
//#define IP_PARSE_SIZE 10
// the 10 for client timeout is 10 seconds
#define CLIENT_TIMEOUT 10 

#include "chatroom.h"
#include "../lib/message.h"
#include "../lib/socket.h"
#include "../lib/serversocket.h"
#include "../lib/unix_exceptions.h"
#include <list>
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <sstream>

using std::cout;
using std::list;
using std::endl;
using std::stringstream;

// GLOBALS
// used for thread management
list<pthread_t*>* GlobalRunningThreads; // gets set to a new list() in main
list<Chatroom*>* GlobalChatrooms;       // gets set to a new list() in main


// FUNCTION DECLERATIONS
void* checkChatrooms(void* noArgument);         // thread function to check chatrooms if they're empty
void* clientConnector(void* clientConnection);  // thread function that asks client for a passphrase 
                                                // and then waits until they disconnect - clientConnection 
                                                // should be a Socket()
//void maintainConnection(Socket* clientSocket, Chatroom* clientsChatroom);   // keeps connection to client 
                                                                            // until they disconnect
//void sendIps(Socket* clientSocket, const set<string>& chatroomsIps);        // sends client list of ips for a chatroom


int main(int argc, char* argv[])
{
    int rc = 0; // default return code for function calls
    GlobalChatrooms = new list<Chatroom*>;
    GlobalRunningThreads = new list<pthread_t*>;

    // setup thread that loops through chatrooms and asks if they're empty and if so deletes them.  
    // Make sure to sleep so no busy polling occurs
    pthread_t chatroomChecker;
    rc = pthread_create(&chatroomChecker, NULL, checkChatrooms, NULL);
    if (0 != rc)
        perror("Could not create chatroomChecker thread.\n");

    // setup socket to listen for connections
    ServerSocket listeningSocket;
    cout << "Listening on port " << listeningSocket.getPort() << endl;

    while(1)
    {   
        struct timeval clientTimeout;
        clientTimeout.tv_sec = CLIENT_TIMEOUT;
        clientTimeout.tv_usec = 0;

        // blocks until a client connects to the 
        // listeningSocket - returns socket set up 
        Socket* clientSocket = new Socket( listeningSocket.acceptSocket(clientTimeout) );

        // create new thread for client
        pthread_t* newThread = (pthread_t*)malloc(sizeof(pthread_t));
        GlobalRunningThreads->push_back(newThread);
        rc = pthread_create(newThread, NULL, clientConnector, (void*)clientSocket); 
        if (0 != rc)
            perror("Could not create clientConnector thread.\n");
    }

    return 0;
}

void* checkChatrooms(void* noArgument)
{
  while(1) // loop forever until server quits
  {
    list<Chatroom*>::iterator i = GlobalChatrooms->begin();
    while( i != GlobalChatrooms->end() )
    {
      if( (*i)->isEmpty() )
        i = GlobalChatrooms->erase( i );
      else
        i++;
    }

    sleep(10);
  }

  pthread_exit(NULL);
}

void* clientConnector(void* clientConnection)   // thread function that asks client for a passphrase 
                                                // and then waits until they disconnect
{
    Socket* clientSocket = (Socket*)clientConnection;

    string clientIp = clientSocket->getAddr();
    int clientPort = clientSocket->getPort();

    Message accessSpecs = clientSocket->recvMsg();

    // Use Fields to get necessary data
    string chatroomName = accessSpecs[0];
    string chatroomPassword = accessSpecs[1];

    cout << "Recieved a request" << endl;
    cout << "Room Name: " << chatroomName << endl;
    cout << "Password: " << chatroomPassword << endl;

    bool chatroomExists = false;
    list<Chatroom*>::iterator curChatroom = GlobalChatrooms->begin();
    while( curChatroom != GlobalChatrooms->end() )
    {
        if ( (*curChatroom)->testAccess(chatroomName, chatroomPassword) )
        {
            // chatroom exists
            chatroomExists = true;
            break;
        }

        curChatroom++;
    }

    if(chatroomExists)
    {
        // add user to it and call function to send message to all chatroom participants about new user 
        // joined chatroom, expect connection soon
        if( (*curChatroom)->isFull() )
        {
            // Send the client a message that the request failed
            Message msg( Message::ServerInfo );
            msg.addField( "Request Failed" );
            msg.addField( "" );
            clientSocket->sendMsg( msg );

            // client has to reconnect to try to enter server again
            clientSocket->closeSock();
            pthread_exit(NULL);
        }
        else
        {
            //Access granted, provide client with IP and port of peers
            set<ClientEntry> ips = (*curChatroom)->getMyConnectedUsersIps();
            if( ips.size() == 0 )
                pthread_exit( NULL );
            set<ClientEntry>::iterator firstUser = ips.begin();
            Message msg( Message::ServerData );
            msg.addField( firstUser->getIP() );

            stringstream ss;
            ss << firstUser->getPort();
            string p = ss.str();

            msg.addField( p );
            clientSocket->sendMsg( msg );

            ClientEntry ce( clientIp, clientPort );
            (*curChatroom)->addUser( ce );

            clientSocket->closeSock();
            pthread_exit( NULL );
        }
    }
    else
    {
        //Create a chatroom
        ClientEntry ce( clientIp, clientPort );
        Chatroom* cr = new Chatroom( chatroomName, chatroomPassword, ce, 2 );
        GlobalChatrooms->push_back( cr );

        cout << "Chatroom Created" << endl;

        try
        {
            // send message to user that new chatroom was created
            Message msg( Message::ServerData );
            msg.addField( "Room Created" );
            msg.addField( "" );
            clientSocket->sendMsg( msg );
        } 
        catch( BaseException* e )
        {
            cout << e->what() << endl;
        }

        clientSocket->closeSock();
        pthread_exit( NULL );
    }

    //maintainConnection(clientSocket, *curChatroom);
    pthread_exit(NULL);
}

/*
void maintainConnection(Socket* clientSocket, Chatroom* clientChatroom)
{
  set<string> clientsChatroomUsersIps = clientChatroom->getMyConnectedUsersIps();
  unsigned int chatroomSyncVersion = 1; // initially set this to a value that will immediately 
                                        // be synced inside the maintain connection loop if the 
                                        // user is not the one who created the list

  // send client a list of ip addresses in groups of IP_PARSE_SIZE deliminated by commas
  while(1)
  {
    // ping client, if no response remove from chatroom, if response check if chatroom has new users and notify client
    int attempts = 0;
    bool tryAgain = true;
    while(attempts < 5 && tryAgain)
    {
      try
      {
        tryAgain = false;
        Message pingBack = clientSocket->recvMsg(); // blocks until recv or timeout
      }
      catch(SocketException& e)
      {
        tryAgain = true;
        attempts++;
      }
    }

    if(tryAgain) // user disconnected
    {
      clientChatroom->removeUserIp( clientSocket->getAddr() );
      clientSocket->closeSock();
      pthread_exit(NULL);
    }
    
    if(chatroomSyncVersion != clientChatroom->getVersion())
    {
      clientsChatroomUsersIps = clientChatroom->getMyConnectedUsersIps();
      clientSocket->sendMsg(Message(INFO_MESSAGE_TYPE, SERVER_NAME, "chatroom updated"));
      
    }
  }
}

void sendIps(Socket* clientSocket, const set<string>& chatroomsIps)
{
  string parsedList = "";
  int numIpsForCurString = 0;
  for(set<string>::iterator i = chatroomsIps.begin(); i != chatroomsIps.end(); i++)
  {
    if( (IP_PARSE_SIZE - 1) == numIpsForCurString )
    {
      parsedList += *i; // don't include a deliminator following ip
      clientSocket->sendMsg(Message(INFO_MESSAGE_TYPE, SERVER_NAME, parsedList));
      numIpsForCurString = 0;
    }

    parsedList += *i + ",";  
    numIpsForCurString++; 
  }

  clientSocket->sendMsg(Message(INFO_MESSAGE_TYPE, SERVER_NAME, "ip updating complete"));
}*/

